/*
 *This code is adapted from Linux source.
 *Please respect GPL if you want to use it.
 *Also, please direct your credits to Linux developpers if you would like to.
 */
#include <cnix/types.h>
#include <cnix/kernel.h>
#include <cnix/pci.h>
#include <cnix/ata.h>

#define PIRQ_SIGNATURE  (('$' << 0) + ('P' << 8) + ('I' << 16) + ('R' << 24))
#define PIRQ_VERSION 0x0100

struct irq_info {
  u8_t bus, devfn;      /* Bus, device and function */
  struct {
    u8_t link;    /* IRQ line ID, chipset dependent,
             0 = not routed */
    u16_t bitmap;   /* Available IRQs */
  } __attribute__((packed)) irq[4];
  u8_t slot;      /* Slot number, 0=onboard */
  u8_t rfu;
} __attribute__((packed));

struct irq_routing_table {
  u32_t signature;      /* PIRQ_SIGNATURE should be here */
  u16_t version;      /* PIRQ_VERSION */
  u16_t size;     /* Table size in bytes */
  u8_t rtr_bus, rtr_devfn;    /* Where the interrupt router lies */
  u16_t exclusive_irqs;   /* IRQs devoted exclusively to
             PCI usage */
  u16_t rtr_vendor, rtr_device; /* Vendor and device ID of
             interrupt router */
  u32_t miniport_data;    /* Crap */
  u8_t rfu[11];
  u8_t checksum;      /* Modulo 256 checksum must give 0 */
  struct irq_info slots[0];
} __attribute__((packed));

static struct irq_routing_table *pirq_table;

/*
 * Never use: 0, 1, 2 (timer, keyboard, and cascade)
 * Avoid using: 13, 14 and 15 (FP error and IDE).
 * Penalize: 3, 4, 6, 7, 12 (known ISA uses: serial, floppy, parallel and mouse)
 */
unsigned int pcibios_irq_mask = 0xfff8;

static int pirq_penalty[16] = {
  1000000, 1000000, 1000000, 1000, 1000, 0, 1000, 1000,
  0, 0, 0, 0, 1000, 100000, 100000, 100000
};

struct irq_router {
  char *name;
  u16_t vendor, device;
  int (*get)(struct pci_device *router, struct pci_device *dev, int pirq);
  int (*set)(struct pci_device *router, struct pci_device *dev, int pirq,
    int new);
};

struct irq_router_handler {
  u16_t vendor;
  int (*probe)(struct irq_router *r, struct pci_device *router, u16_t device);
};

static struct irq_router pirq_router;
static struct pci_device *pirq_router_dev=NULL;

/*
 *  Check passed address for the PCI IRQ Routing Table signature
 *  and perform checksum verification.
 */
static inline struct irq_routing_table *pirq_check_routing_table(u8_t *addr)
{
  struct irq_routing_table *rt;
  int i;
  u8_t sum;

  rt = (struct irq_routing_table *) addr;
  if (rt->signature != PIRQ_SIGNATURE ||
      rt->version != PIRQ_VERSION ||
      rt->size % 16 ||
      rt->size < sizeof(struct irq_routing_table))
    return NULL;
  sum = 0;
  for (i = 0; i < rt->size; i++)
    sum += addr[i];
  if (!sum) {
    printk("PCI: Interrupt Routing Table found at %x\n",(unsigned)rt);
    return rt;
  } 
  return NULL;
}

/*
 *  Search 0xf0000 -- 0xfffff for the PCI IRQ Routing Table.
 */
static struct irq_routing_table * pirq_find_routing_table(void)
{
  u8_t *addr;
  struct irq_routing_table *rt; 

  for (addr = (u8_t *) KERN_VA(0xf0000); addr < (u8_t *) KERN_VA(0x100000); addr += 16) {
    rt = pirq_check_routing_table(addr);
    if (rt) 
      return rt;
  }
  return NULL;
}

static struct irq_info *pirq_get_info(struct pci_device *dev)
{
  struct irq_routing_table *rt = pirq_table;
  int entries = (rt->size - sizeof(struct irq_routing_table)) /
    sizeof(struct irq_info);
  struct irq_info *info;

  for (info = rt->slots; entries--; info++)
    if (info->bus == dev->bus->busnr &&
      PCI_SLOT(info->devfn) == PCI_SLOT(dev->devfn))
      return info;
  return NULL;
}

static int pcibios_lookup_irq(struct pci_device *pdev)
{
	u8_t pin;
	struct irq_info *info;
	int pirq;
	u32_t mask;

  /* Find IRQ pin */
  pci_read_config_byte(pdev->bus, pdev->devfn, PCI_INTERRUPT_PIN, &pin);
  if (!pin) {
    printk("%s: no interrupt pin %d.\n",__func__,(int)pdev->pin);
		return 0;
  }

  info = pirq_get_info(pdev);
  if (!info) {
    printk("PCI INT %c not found in routing table\n",
      'A' + pin - 1);
    while(1);
  }
  pirq = info->irq[pin - 1].link;
  mask = info->irq[pin - 1].bitmap;
  if (!pirq) {
    printk("PCI INT %c not routed\n", 'A' + pin - 1);
    while(1);
  }
  mask &= pcibios_irq_mask;

	return 1;	
}

void pirq_enable_irq_ata(struct pci_device *pdev)
{
	pcibios_lookup_irq(pdev);
	printk("ATA pin: %d irq: %d\n",(int)pdev->pin,(int)pdev->irq);
}

/*
 *service routines for irq_router_handler
 */
/*
  static struct pci_device_id __initdata pirq_440gx[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443GX_0) },
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443GX_2) },
    { },
  };
  // 440GX has a proprietary PIRQ router -- don't use it
  if (pci_dev_present(pirq_440gx))
    return 0;
*/
static int pci_dev_present()
{
	if(find_dev_by_vendor(PCI_VENDOR_ID_INTEL,PCI_DEVICE_ID_INTEL_82443GX_0) ||
		 find_dev_by_vendor(PCI_VENDOR_ID_INTEL,PCI_DEVICE_ID_INTEL_82443GX_2))
		return 1;
	else return 0;
}

/*
 * The Intel PIIX4 pirq rules are fairly simple: "pirq" is
 * just a pointer to the config space.
 */
static int pirq_piix_get(struct pci_device *router, struct pci_device *dev, int pirq)
{
  u8_t x;

  pci_read_config_byte(router->bus,router->devfn,pirq,&x);
  return (x < 16) ? x : 0;
}

static int pirq_piix_set(struct pci_device *router, struct pci_device *dev, int pirq, int irq)
{
  pci_write_config_byte(router->bus,router->devfn,pirq, irq);
  return 1;
}

static int intel_router_probe(struct irq_router *r, struct pci_device *router, u16_t device)
{

	if(pci_dev_present())
		return 0;
  switch (device) {
  case PCI_DEVICE_ID_INTEL_82371FB_0:
  case PCI_DEVICE_ID_INTEL_82371SB_0:
  case PCI_DEVICE_ID_INTEL_82371AB_0:
  case PCI_DEVICE_ID_INTEL_82371MX:
  case PCI_DEVICE_ID_INTEL_82443MX_0:
  case PCI_DEVICE_ID_INTEL_82801AA_0:
  case PCI_DEVICE_ID_INTEL_82801AB_0:
  case PCI_DEVICE_ID_INTEL_82801BA_0:
  case PCI_DEVICE_ID_INTEL_82801BA_10:
  case PCI_DEVICE_ID_INTEL_82801CA_0:
  case PCI_DEVICE_ID_INTEL_82801CA_12:
  case PCI_DEVICE_ID_INTEL_82801DB_0:
  case PCI_DEVICE_ID_INTEL_82801E_0:
  case PCI_DEVICE_ID_INTEL_82801EB_0:
  case PCI_DEVICE_ID_INTEL_ESB_1:
  case PCI_DEVICE_ID_INTEL_ICH6_0:
  case PCI_DEVICE_ID_INTEL_ICH6_1:
  case PCI_DEVICE_ID_INTEL_ICH7_0:
  case PCI_DEVICE_ID_INTEL_ICH7_1:
  case PCI_DEVICE_ID_INTEL_ICH7_30:
  case PCI_DEVICE_ID_INTEL_ICH7_31:
  case PCI_DEVICE_ID_INTEL_TGP_LPC:
  case PCI_DEVICE_ID_INTEL_ESB2_0:
  case PCI_DEVICE_ID_INTEL_ICH8_0:
  case PCI_DEVICE_ID_INTEL_ICH8_1:
  case PCI_DEVICE_ID_INTEL_ICH8_2:
  case PCI_DEVICE_ID_INTEL_ICH8_3:
  case PCI_DEVICE_ID_INTEL_ICH8_4:
  case PCI_DEVICE_ID_INTEL_ICH9_0:
  case PCI_DEVICE_ID_INTEL_ICH9_1:
  case PCI_DEVICE_ID_INTEL_ICH9_2:
  case PCI_DEVICE_ID_INTEL_ICH9_3:
  case PCI_DEVICE_ID_INTEL_ICH9_4:
  case PCI_DEVICE_ID_INTEL_ICH9_5:
  case PCI_DEVICE_ID_INTEL_TOLAPAI_0:
  case PCI_DEVICE_ID_INTEL_ICH10_0:
  case PCI_DEVICE_ID_INTEL_ICH10_1:
  case PCI_DEVICE_ID_INTEL_ICH10_2:
  case PCI_DEVICE_ID_INTEL_ICH10_3:
    r->name = "PIIX/ICH";
    r->get = pirq_piix_get;
    r->set = pirq_piix_set;
    return 1;
  }

  if ((device >= PCI_DEVICE_ID_INTEL_PCH_LPC_MIN) &&
    (device <= PCI_DEVICE_ID_INTEL_PCH_LPC_MAX)) {
    r->name = "PIIX/ICH";
    r->get = pirq_piix_get;
    r->set = pirq_piix_set;
    return 1;
  }

  return 0;
}

static struct irq_router_handler pirq_routers[] = {
  { PCI_VENDOR_ID_INTEL, intel_router_probe },
#if 0
  { PCI_VENDOR_ID_AL, ali_router_probe },
  { PCI_VENDOR_ID_ITE, ite_router_probe },
  { PCI_VENDOR_ID_VIA, via_router_probe },
  { PCI_VENDOR_ID_OPTI, opti_router_probe },
  { PCI_VENDOR_ID_SI, sis_router_probe },
  { PCI_VENDOR_ID_CYRIX, cyrix_router_probe },
  { PCI_VENDOR_ID_VLSI, vlsi_router_probe },
  { PCI_VENDOR_ID_SERVERWORKS, serverworks_router_probe },
  { PCI_VENDOR_ID_AMD, amd_router_probe },
  { PCI_VENDOR_ID_PICOPOWER, pico_router_probe },
#endif
  { 0, NULL }
};

static void pirq_find_router(struct irq_router *r)
{
	struct irq_routing_table *rt = pirq_table;
	struct irq_router_handler *h;

  /* Default unless a driver reloads it */
  r->name = "default";
  r->get = NULL;
  r->set = NULL;

	printk("PCI: Attempting to find IRQ router for [%04x:%04x]\n",rt->rtr_vendor, rt->rtr_device);

	pirq_router_dev = find_dev(0,rt->rtr_bus, rt->rtr_devfn);
  if (!pirq_router_dev) {
    printk("pirq_find_router: Interrupt router not found at "
      "%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
    while(1);
  }

  for (h = pirq_routers; h->vendor; h++) {
    /* First look for a router match */
    if (rt->rtr_vendor == h->vendor &&
      h->probe(r, pirq_router_dev, rt->rtr_device))
      break;
    /* Fall back to a device match */
    if (pirq_router_dev->vendor == h->vendor &&
      h->probe(r, pirq_router_dev, pirq_router_dev->device))
      break;
  }
  printk("%s IRQ router [%04x:%04x]\n",
     pirq_router.name,
     pirq_router_dev->vendor, pirq_router_dev->device);
}

void pcibios_irq_init(void)
{
	pirq_table = pirq_find_routing_table();
  if (!pirq_table){
		printk("Sorry,failed to find a pirq_table. Please try bios functions.\n");
		while(1);
	}

  if (pirq_table) {
		pirq_find_router(&pirq_router);
    if (pirq_table->exclusive_irqs) {
      int i; 
      for (i = 0; i < 16; i++)
        if (!(pirq_table->exclusive_irqs & (1 << i)))
          pirq_penalty[i] += 100;
    }
   } 
}
